<script setup lang="ts">
import { onMounted } from "vue";
import { ref, reactive } from "vue";
import { cloneDeep } from "@pureadmin/utils";
import searchButtons from "../commonSearch/searchButtons.vue";
import type { SelectDataType, TreeDataType } from "@/api/type";
import { queryChildByTypeCodeAndItemValue } from "@/api/solidity";

const loading = defineModel("loading", { type: Boolean, default: false });

interface Props {
  /**  输入项配置 */
  option?: Array<CommonSearch.SearchOption>;
  /**  查询时 忽略的字段 数组类型 */
  ignoreKeys?: Array<string>;
  /**  表单校验，同 px-form rules */
  rules?: CommonSearch.SearchFormRulesType;
}

const props = withDefaults(defineProps<Props>(), {});

interface Emits {
  /**  查询事件触发 */
  (event: "submit", form: any): void;
  /**  重置事件触发 */
  (event: "reset", form: any): void;
}

const emits = defineEmits<Emits>();

const searchFormRef = ref<CommonSearch.SearchFormRefType>(null);
const commonSearchForm = reactive<Record<string, any>>({});
const selectOptions = reactive<Record<string, SelectDataType>>({});
const treeSelectData = reactive<Record<string, TreeDataType>>({});
const defaultTime = ref<Array<Date>>([new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]);
const selectLoading = ref<boolean>(false);

const getRealSelectOptions = async (): Promise<undefined> => {
  loading.value = true;
  if (!props.option?.length) return;
  for (const item of props.option) {
    const {
      axiosApi = null,
      params = null,
      option = [],
      formatOption = null,
      mapOption = null,
      filterOption = null,
      key = "",
      typeCode = "",
      type = "",
      remote = false,
      relationDefault = false,
      relation = "",
      relationApi = null
    } = item;
    if ((type !== "select" && type !== "tree-select") || (!axiosApi && !option)) continue;
    if (option?.length) {
      selectOptions[key] = formatOption ? formatOption(option) : mapOption ? option.map(mapOption) : option;
      if (filterOption && typeof filterOption === "function") {
        selectOptions[key] = selectOptions[key].filter(filterOption);
      }
      continue;
    }
    if (axiosApi && !remote) {
      try {
        const res = await axiosApi(params);
        if (res?.length) {
          if (type === "select") {
            selectOptions[key] = formatOption
              ? formatOption(res)
              : mapOption
                ? res.map(mapOption)
                : res.map(item => ({ ...item, label: item.name, value: item.id }));
            if (filterOption && typeof filterOption === "function") {
              selectOptions[key] = selectOptions[key].filter(filterOption);
            }
          } else if (type === "tree-select") {
            treeSelectData[key] = formatOption ? formatOption(res) : mapOption ? res.map(mapOption) : res.map(item => item);
          }
        } else {
          if (type === "select") {
            selectOptions[key] = [];
          } else if (type === "tree-select") {
            treeSelectData[key] = [];
          }
        }
      } catch (error) {
        console.error(error);
      }
      continue;
    }
    if (relation && !relationApi) {
      const item = props.option.find(item => item.key === relation);
      if (item.value) {
        await selectChangeHandler(item.value as string, item);
        continue;
      } else if (relationDefault) {
        await selectChangeHandler("", item);
        continue;
      }
    }
    if (typeCode) {
      try {
        const res: Array<any> = await queryChildByTypeCodeAndItemValue({ typeCode: typeCode });
        if (res?.length) {
          selectOptions[key] = res.map(item => ({ ...item, label: item.itemName, value: item.itemValue }));
        } else {
          selectOptions[key] = [];
        }
      } catch (error) {
        console.error(error);
      }
      continue;
    }
    selectOptions[key] = [];
    continue;
  }
  loading.value = false;
};

const selectChangeHandler = async (val: string | number, item: any): Promise<undefined> => {
  const {
    relation = "",
    relationType = "",
    relationApi = null,
    getParams = null,
    formatOption = null,
    relationMapOption = null,
    relationFilterOption = null
  } = item;
  if (!relation) return;
  if (!relationApi) return;
  treeSelectData[relation] = [];
  selectOptions[relation] = [];
  commonSearchForm[relation] = "";
  try {
    const res = await relationApi(getParams ? getParams(val) : null);
    if (relationType === "tree-select") {
      if (res?.length) {
        treeSelectData[relation] = res;
      }
    } else {
      if (res?.length) {
        selectOptions[relation] = formatOption
          ? formatOption(res)
          : relationMapOption
            ? res.map(relationMapOption)
            : res.map(item => ({ ...item, label: item.name, value: item.id }));
        if (relationFilterOption && typeof relationFilterOption === "function") {
          selectOptions[relation] = selectOptions[relation].filter(relationFilterOption);
        }
      }
    }
  } catch (error) {
    console.error(error);
  }
};

const selectRemoteMethod = async (query: string = "", item: Record<string, any> = {}): Promise<void> => {
  const { key = "", selectRemoteApi = null, getParams = null, formatOption = null, mapOption = null, filterOption = null } = item;
  selectOptions[key] = [];
  if (query) {
    selectLoading.value = true;
    const res = await selectRemoteApi(getParams ? getParams(query) : null);
    if (res?.length) {
      selectOptions[key] = formatOption
        ? formatOption(res)
        : mapOption
          ? res.map(mapOption)
          : res.map(item => ({ ...item, label: item.name, value: item.id }));
      if (filterOption && typeof filterOption === "function") {
        selectOptions[key] = selectOptions[key].filter(filterOption);
      }
    }
    selectLoading.value = false;
  }
};

const initSearchForm = (f?: boolean): undefined => {
  if (!f) return;
  if (!props.option?.length) return;
  for (const item of props.option) {
    const { key, type, value } = item;
    switch (type) {
      case "daterange":
      case "datetimerange":
        commonSearchForm[key] = value || value === null ? value : [];
        break;
      default:
        commonSearchForm[key] = value || value === null || value === 0 ? value : "";
        break;
    }
  }
};

const getFormParams = (): Record<string, any> => {
  function mapParams<T extends Record<string, any>>(p: T) {
    return props.option.reduce((pre: Record<string, any>, cur: CommonSearch.SearchOption): Record<string, any> => {
      const { key = "", realKeys = null } = cur;
      if (realKeys?.length) {
        pre[realKeys[0]] = p[key]?.length ? p[key][0] : "";
        pre[realKeys[1]] = p[key]?.length ? p[key][1] : "";
      } else {
        pre[key] = p[key];
      }
      return pre;
    }, {});
  }
  if (props.ignoreKeys?.length) {
    const form = {};
    for (const key in commonSearchForm) {
      if (!props.ignoreKeys.includes(key)) {
        form[key] = commonSearchForm[key];
      }
    }
    return mapParams(cloneDeep(form));
  } else {
    return mapParams(cloneDeep(commonSearchForm));
  }
};

const submitHandler = (): undefined => {
  const form = getFormParams();
  emits("submit", form);
};

const resetHandler = async (f?: boolean): Promise<void> => {
  await getRealSelectOptions();
  initSearchForm(f);
  const form = getFormParams();
  emits("reset", form);
};

const reset = (): undefined => {
  if (!searchFormRef.value) return;
  searchFormRef.value.resetFields();
  searchFormRef.value.clearValidate();
  initSearchForm(true);
};

onMounted(async () => {
  await getRealSelectOptions();
  initSearchForm(true);
  submitHandler();
});

defineExpose({
  el: searchFormRef,
  initSearchForm,
  reset
});
</script>

<template>
  <px-form ref="searchFormRef" class="common-search-form" :model="commonSearchForm" :rules="rules">
    <px-form-item
      v-for="item in option"
      :key="item.key"
      :label="item.label || ''"
      :prop="item.key"
      class="search-form-item"
      :class="{ 'search-form-sitem': item.type === 'daterange' || item.type === 'datetimerange' }"
    >
      <template v-if="item.type === 'input'">
        <px-input v-model.trim="commonSearchForm[item.key]" :placeholder="item.placeholder" clearable v-bind="item.props" />
      </template>
      <template v-if="item.type === 'select'">
        <px-select
          v-if="item.relation"
          v-model="commonSearchForm[item.key]"
          :placeholder="item.placeholder"
          clearable
          filterable
          v-bind="item.props"
          @change="selectChangeHandler($event, item)"
        >
          <px-option v-for="s in selectOptions[item.key]" :key="s.value" :label="s.label" :value="s.value" />
        </px-select>
        <px-select
          v-else-if="item.remote"
          v-model="commonSearchForm[item.key]"
          clearable
          filterable
          remote
          reserve-keyword
          remote-show-suffix
          :placeholder="item.placeholder"
          :remote-method="q => selectRemoteMethod(q, item)"
          :loading="selectLoading"
          v-bind="item.props"
        >
          <px-option v-for="s in selectOptions[item.key]" :key="s.value" :label="s.label" :value="s.value" />
        </px-select>
        <px-select v-else v-model="commonSearchForm[item.key]" :placeholder="item.placeholder" clearable v-bind="item.props" filterable>
          <px-option v-for="s in selectOptions[item.key]" :key="s.value" :label="s.label" :value="s.value" />
        </px-select>
      </template>
      <template v-if="item.type === 'tree-select'">
        <px-tree-select
          v-model="commonSearchForm[item.key]"
          clearable
          filterable
          check-strictly
          highlight-current
          :node-key="`${item.nodeKey || 'id'}`"
          :props="item.treeSelectProps"
          :data="item.data"
          :render-after-expand="false"
          :placeholder="item.placeholder"
          v-bind="item.props"
        />
      </template>
      <template v-if="item.type === 'date'">
        <px-date-picker
          v-model="commonSearchForm[item.key]"
          type="date"
          :editable="false"
          :placeholder="item.placeholder"
          format="YYYY-MM-DD"
          value-format="YYYY-MM-DD"
          v-bind="item.props"
        />
      </template>
      <template v-if="item.type === 'daterange'">
        <px-date-picker
          v-model="commonSearchForm[item.key]"
          type="daterange"
          :editable="false"
          range-separator="-"
          start-placeholder="开始日期"
          end-placeholder="结束日期"
          format="YYYY-MM-DD"
          value-format="YYYY-MM-DD HH:mm:ss"
          :default-time="defaultTime"
          v-bind="item.props"
        />
      </template>
      <template v-if="item.type === 'datetimerange'">
        <px-date-picker
          v-model="commonSearchForm[item.key]"
          type="datetimerange"
          :editable="false"
          range-separator="-"
          :prefix-icon="null"
          start-placeholder="开始时间"
          end-placeholder="结束时间"
          format="YYYY-MM-DD HH:mm:ss"
          value-format="YYYY-MM-DD HH:mm:ss"
          :default-time="defaultTime"
          v-bind="item.props"
        />
      </template>
    </px-form-item>
    <px-form-item class="search-btn-form-item">
      <searchButtons :formRef="searchFormRef" :rules="rules" @submit="submitHandler" @reset="resetHandler" />
    </px-form-item>
  </px-form>
</template>

<style lang="scss" scoped>
.common-search-form {
  display: flex;
  gap: 16px;
  align-items: center;

  .px-row {
    row-gap: 16px;
    margin-right: 0 !important;
  }

  :deep(.px-form-item) {
    flex: 2;
    margin-bottom: 0;

    &.search-form-sitem {
      flex: 3;
    }

    &.search-btn-form-item {
      flex: 0;
    }

    &:not(.search-btn-form-item) {
      overflow: hidden;
      background-color: #f2f2f2;
      border: 1px solid #dfdfdf;
      border-radius: 4px;
    }

    .px-form-item__label {
      padding: 0 14px;
      font-weight: 500;
      color: #555;
    }

    .px-form-item__content {
      .px-input,
      .px-select,
      .px-date-picker,
      .px-time-picker,
      .px-tree-select {
        width: 100%;
        height: 100%;

        .select-trigger {
          height: 100%;
        }
      }

      .px-input__wrapper {
        border: none;

        &.is-focus {
          border: none;
          box-shadow: none;
        }
      }

      .px-input__icon.px-range__icon,
      .px-input__prefix {
        display: none;
      }

      .px-date-editor .px-range-input {
        width: 46%;
      }
    }
  }
}
</style>
